<?php
namespace app\helpers;

/**
 * 模拟HTTP请求类，通过 CURL 发起请求
 * @author lijing151    拷贝自libs下，并修改成默认毫秒级别的超时
 */
class XapiHttpHandler
{
    const CHARSET_UTF8 = 'UTF-8';
    const CHARSET_GBK = 'GBK';
    const DEFAULT_TIMEOUT = 1000;
    private $responseCharset;
    private $ch;

    /**
     * 构造函数
     * @param string $responseCharset 响应字符集。非UTF-8的站重写此变量
     * @param int $timeout 超时时间
     */
    public function __construct($responseCharset = self::CHARSET_UTF8, $timeout_ms = 0)
    {
        $this->responseCharset = $responseCharset;

        if ($timeout_ms == 0) {
            global $g_switch_conf;
            if (isset($g_switch_conf['GATEWAY_CURL_TIMEOUT_MS']) && $g_switch_conf['GATEWAY_CURL_TIMEOUT_MS'] > 0) {
                $timeout_ms = $g_switch_conf['GATEWAY_CURL_TIMEOUT_MS'];
            } else {
                $timeout_ms = self::DEFAULT_TIMEOUT;
            }
        }

        $this->ch = curl_init();

        $opt = array(
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_FOLLOWLOCATION => false,
            //因为生成tmp下文件太多，且液态和bd后台以及飞凡主站不使用，故暂时屏蔽
            //CURLOPT_COOKIEJAR => tempnam('/tmp/', 'XapiHttpHandler_Cookie_'),
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_TIMEOUT_MS => $timeout_ms,
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
        );

        curl_setopt_array($this->ch, $opt);
    }

    public function __destruct()
    {
        curl_close($this->ch);
    }

    /**
     * 任意编码转为utf8，接收response后调用
     *
     * @param mixed $anyData
     *
     * @return mixed
     */
    private function iconvAny2utf8($anyData)
    {
        $ret = $anyData;

        if ($this->responseCharset != self::CHARSET_UTF8) {
            $ret = self::iconvAny($this->responseCharset, self::CHARSET_UTF8, $anyData);
        }

        return $ret;
    }

    /**
     * utf8转为任意编码，向外发request时调用
     *
     * @param mixed $utf8Data
     *
     * @return mixed
     */
    private function iconvUtf82Any($utf8Data)
    {
        $ret = $utf8Data;

        if ($this->responseCharset != self::CHARSET_UTF8) {
            $ret = self::iconvAny(self::CHARSET_UTF8, $this->responseCharset, $utf8Data);
        }

        return $ret;
    }

    /**
     * 字符集转换，支持字符串和数组类型
     *
     * @param string $in_charset
     * @param string $out_charset
     * @param mixed $data
     * @return mixed
     */
    private static function iconvAny($in_charset, $out_charset, $data)
    {
        if (is_array($data)) {
            //数组，递归处理
            $ret = array();
            foreach ($data as $k => $v) {
                $ret[self::iconvAny($in_charset, $out_charset, $k)] = self::iconvAny($in_charset, $out_charset, $v);
            }
        } else {
            // 字符串，iconv转码
            $ret = iconv($in_charset, $out_charset . "//IGNORE", $data);
            // 字符串iconv转码失败，尝试用 mb_convert_encoding 转码
            if ($data !== false && $ret === false) {
                $ret = mb_convert_encoding($data, $out_charset, $in_charset);
            }
        }

        return $ret;
    }

    private function _exec($opt, $func_name)
    {
        $start = microtime(TRUE);

        curl_setopt_array($this->ch, $opt);
        $data = curl_exec($this->ch);
        if($data==false){
            $data = curl_exec($this->ch);
        }
        $end = microtime(TRUE);
        $runtime_ms = intval((($end - $start) * 1000000)) / 1000;

        // 错误整理
        $err_no = curl_errno($this->ch);
        $err_msg = $this->error_codes[$err_no];
        $ret = array();

        $method = strtoupper($func_name);
        global $g_switch_conf;

        // 出现curl操作错误
        if ($err_no) {
            // curl操作错误，转换成"521:接口调用异常"
            $ret['status'] = 521;
            $ret['msg'] = $err_msg;
            $data = json_encode($ret);
        } // curl操作成功，判断内部返回各种状态
        else {
            $info = curl_getinfo($this->ch);
            // 当http_code非200时，转换成500报错
            if ($info['http_code'] != 200) {
                $ret = array(
                    'status' => 549,
                    'message' => '网络异常',
                    'data' => '',
                );
                $data = json_encode($ret);
            } // 成功状态下判断返回数据
            else {
                // 返回内容为空
                if (empty($data)) {
                    $ret = array(
                        'status' => 521,
                        'message' => '接口调用异常，curl返回结果为空',
                        'data' => '',
                    );
                    $data = json_encode($ret);
                } else {
                    $tmp = json_decode($data, true);
                    if (isset($tmp['status']) && !is_numeric($tmp['status'])) {
                        $ret = array(
                            'status' => 521,
                            'message' => '接口调用异常，无响应数据',
                            'data' => '',
                        );
                        $data = json_encode($ret);
                    } elseif ((isset($tmp['status']) && in_array($tmp['status'], array(404, 502, 504, 500)))) {
                        $ret = array(
                            'status' => $tmp['status'],
                            'message' => $tmp['message'] ?: '后端业务处理异常',
                            'data' => '',
                        );
                        $data = json_encode($ret);
                    }
                }
            }
        }

        $data = $this->iconvAny2utf8($data);
        return $data;
    }

    /**
     * HTTP GET
     *
     * @todo $headers 未处理
     *
     * @param string $url
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function get($url, array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url);
        $url = $result['url'];

        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 0,
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            //$opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    /**
     * HTTP POSTJson
     *
     * @todo $headers 未处理
     *
     * @param string $url
     * @param array $query
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function postJson($url, array $query, array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url, $query);
        $url = $result['url'];
        $query = $result['query'];

        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => json_encode($query, true),
            CURLOPT_HTTPHEADER => array(
                'Content-Type: application/json',
                'Content-Length: ' . strlen(json_encode($query, true))),

        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    /**
     * HTTP POST
     *
     * @todo $headers 未处理
     *
     * @param string $url
     * @param array $query
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function post($url, array $query, array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url, $query);
        $url = $result['url'];
        $query = $result['query'];
        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => http_build_query((array)$query),
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    /**
     *
     * @param array $requests
     *            = array(
     *            array('url' => 'http://1', 'timeout' => '', 'host' => ''),
     *            array('url' => 'http://1', 'timeout' => '', 'host' => '')
     *            );
     * @param bool $time_ms true--s, false--ms
     * @return array $results = array(
     *         'content1',
     *         'content1',
     *         );
     */
    public function multiRequest($requests, $time_ms = false)
    {
        $mh = curl_multi_init();
        $handles = array();
        $results = array();
        $map = array();
        foreach ($requests as $key => $value) {
            if ($value['url']) {
                $result = $this->transferpuid($value['url']);
                $handles[$key] = curl_init($result['url']);
                if ($value['timeout']) {
                    if (true === $time_ms) {
                        curl_setopt($handles[$key], CURLOPT_NOSIGNAL, 1);
                        if (empty($value['timeout'])) {
                            $requests[$key]['timeout'] = $value['timeout'] = 1000;
                        }
                        curl_setopt($handles[$key], CURLOPT_TIMEOUT_MS, $value['timeout']);
                        curl_setopt($handles[$key], CURLOPT_CONNECTTIMEOUT_MS, ENV === 'sit' ? 500 : 100);  //尝试连接等待的时间
                        // curl_setopt($handles[$key], CURLE_OPERATION_TIMEOUTED, 1);  //尝试连接等待的时间
                    } else {
                        if (empty($value['timeout'])) {
                            $requests[$key]['timeout'] = $value['timeout'] = 1;
                        }
                        curl_setopt($handles[$key], CURLOPT_TIMEOUT, $value['timeout']);
                        curl_setopt($handles[$key], CURLOPT_CONNECTTIMEOUT, 1);  //尝试连接等待的时间

                    }
                } else {
                    curl_setopt($handles[$key], CURLOPT_TIMEOUT, 1);
                    curl_setopt($handles[$key], CURLOPT_CONNECTTIMEOUT, 1);  //尝试连接等待的时间
                }

                curl_setopt($handles[$key], CURLOPT_FAILONERROR, 1);
                curl_setopt($handles[$key], CURLOPT_RETURNTRANSFER, true);
                if ($value['data']) {
                    curl_setopt($handles[$key], CURLOPT_POST, 1);
                    curl_setopt($handles[$key], CURLOPT_POSTFIELDS, $value['data']);
                }
                curl_multi_add_handle($mh, $handles[$key]);
                $map[(int)$handles[$key]] = array('key' => $key, 'value' => $value);
            } else {
                unset($requests[$key]);
            }
        }

        if (empty($handles)) {
            curl_multi_close($mh);
            return $results;
        }

        $responses = array();
        do {
            while (($code = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM) ;

            if ($code != CURLM_OK) {
                break;
            }
            $done = '';
            while ($done = curl_multi_info_read($mh)) {
                $info = curl_getinfo($done['handle']);
                $error = curl_error($done['handle']);
                $errno = curl_errno($done['handle']);
                $results[$map[(int)$done['handle']]['key']] = curl_multi_getcontent($done['handle']);
                // remove the curl handle that just completed
                curl_multi_remove_handle($mh, $done['handle']);
                curl_close($done['handle']);
            }

            // Block for data in / output; error handling is done by curl_multi_exec
            if ($active > 0) {
                curl_multi_select($mh, 0.5);
            }

        } while ($active);

        curl_multi_close($mh);
        return $results;
    }

    /**
     * HTTP PUT
     *
     * @todo $headers 未处理
     *
     * @param string $url
     * @param array $query
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function put($url, array $query, array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url, $query);
        $url = $result['url'];
        $query = $result['query'];
        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 0,
            CURLOPT_CUSTOMREQUEST => 'PUT',
            CURLOPT_POSTFIELDS => http_build_query((array)$query),
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    public function putJson($url, array $query, array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url, $query);
        $url = $result['url'];
        $query = $result['query'];

        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 0,
            CURLOPT_CUSTOMREQUEST => 'PUT',
            CURLOPT_POSTFIELDS => json_encode($query),
            CURLOPT_HTTPHEADER => array(
                'Content-Type:application/json;charset=utf-8',
                'Accept:application/json',
            ),
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    /**
     * HTTP DELETE
     *
     * @param string $url
     * @param array /string $query
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function delete($url, $query = '', array $headers = array(), array $opts = array())
    {
        $result = $this->transferpuid($url, $query);
        $url = $result['url'];
        $query = $result['query'];
        $query = (is_array($query)) ? http_build_query($query) : $query; // 保证string

        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_FOLLOWLOCATION => 1,
            CURLOPT_CUSTOMREQUEST => 'DELETE',
            CURLOPT_POSTFIELDS => $query, // string
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    /**
     * HTTP POST 上传文件binary流
     *
     * @todo $headers 未处理
     * @author xielei14
     *
     * @param string $url
     * @param array $data
     * @param array $headers
     * @param array $opts array[cookies, header]
     * @return string
     */
    public function postFile($url, $data, array $headers = array(), array $opts = array())
    {
        $opt = array(
            CURLOPT_URL => $this->iconvUtf82Any($url),
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => $data,
        );

        if (isset($_SERVER['HTTP_COOKIE'])) {
            $opt[CURLOPT_COOKIE] = $_SERVER['HTTP_COOKIE'];
        }

        if (isset($opts['cookies']) && $opts['cookies']) {
            $opt[CURLOPT_COOKIE] .= '; ' . $opts['cookies'];
        }

        if (isset($opts['header']) && $opts['header']) {
            $opt[CURLOPT_HEADER] = true;
        }

        return $this->_exec($opt, __FUNCTION__);
    }

    //xapi接口透传puid
    public function transferpuid($url, $data = array())
    {
        //增加每个请求的trace_id, 方便追踪请求去向
        if (stripos($url, '__trace_id') == false) {
            if (!empty($_GET['__trace_id'])) {
                $url .= (stripos($url, '?') !== false) ? "&__trace_id={$_GET['__trace_id']}" : "?__trace_id={$_GET['__trace_id']}";
            }
        }

        //增加每个请求的__uni_source, 方便追踪來源請求
        $url .= (stripos($url, '?') !== false) ? "&__uni_source=4.2.1" : "?__uni_source=4.2.1";

        $result = array(
            'url' => $url,
            'query' => $data
        );

        return $result;
    }

    private $error_codes = array(
        0 => 'CURLE_SUCCESS',
        1 => 'CURLE_UNSUPPORTED_PROTOCOL',
        2 => 'CURLE_FAILED_INIT',
        3 => 'CURLE_URL_MALFORMAT',
        4 => 'CURLE_URL_MALFORMAT_USER',
        5 => 'CURLE_COULDNT_RESOLVE_PROXY',
        6 => 'CURLE_COULDNT_RESOLVE_HOST',
        7 => 'CURLE_COULDNT_CONNECT',
        8 => 'CURLE_FTP_WEIRD_SERVER_REPLY',
        9 => 'CURLE_REMOTE_ACCESS_DENIED',
        11 => 'CURLE_FTP_WEIRD_PASS_REPLY',
        13 => 'CURLE_FTP_WEIRD_PASV_REPLY',
        14 => 'CURLE_FTP_WEIRD_227_FORMAT',
        15 => 'CURLE_FTP_CANT_GET_HOST',
        17 => 'CURLE_FTP_COULDNT_SET_TYPE',
        18 => 'CURLE_PARTIAL_FILE',
        19 => 'CURLE_FTP_COULDNT_RETR_FILE',
        21 => 'CURLE_QUOTE_ERROR',
        22 => 'CURLE_HTTP_RETURNED_ERROR',
        23 => 'CURLE_WRITE_ERROR',
        25 => 'CURLE_UPLOAD_FAILED',
        26 => 'CURLE_READ_ERROR',
        27 => 'CURLE_OUT_OF_MEMORY',
        28 => 'CURLE_OPERATION_TIMEDOUT',
        30 => 'CURLE_FTP_PORT_FAILED',
        31 => 'CURLE_FTP_COULDNT_USE_REST',
        33 => 'CURLE_RANGE_ERROR',
        34 => 'CURLE_HTTP_POST_ERROR',
        35 => 'CURLE_SSL_CONNECT_ERROR',
        36 => 'CURLE_BAD_DOWNLOAD_RESUME',
        37 => 'CURLE_FILE_COULDNT_READ_FILE',
        38 => 'CURLE_LDAP_CANNOT_BIND',
        39 => 'CURLE_LDAP_SEARCH_FAILED',
        41 => 'CURLE_FUNCTION_NOT_FOUND',
        42 => 'CURLE_ABORTED_BY_CALLBACK',
        43 => 'CURLE_BAD_FUNCTION_ARGUMENT',
        45 => 'CURLE_INTERFACE_FAILED',
        47 => 'CURLE_TOO_MANY_REDIRECTS',
        48 => 'CURLE_UNKNOWN_TELNET_OPTION',
        49 => 'CURLE_TELNET_OPTION_SYNTAX',
        51 => 'CURLE_PEER_FAILED_VERIFICATION',
        52 => 'CURLE_GOT_NOTHING',
        53 => 'CURLE_SSL_ENGINE_NOTFOUND',
        54 => 'CURLE_SSL_ENGINE_SETFAILED',
        55 => 'CURLE_SEND_ERROR',
        56 => 'CURLE_RECV_ERROR',
        58 => 'CURLE_SSL_CERTPROBLEM',
        59 => 'CURLE_SSL_CIPHER',
        60 => 'CURLE_SSL_CACERT',
        61 => 'CURLE_BAD_CONTENT_ENCODING',
        62 => 'CURLE_LDAP_INVALID_URL',
        63 => 'CURLE_FILESIZE_EXCEEDED',
        64 => 'CURLE_USE_SSL_FAILED',
        65 => 'CURLE_SEND_FAIL_REWIND',
        66 => 'CURLE_SSL_ENGINE_INITFAILED',
        67 => 'CURLE_LOGIN_DENIED',
        68 => 'CURLE_TFTP_NOTFOUND',
        69 => 'CURLE_TFTP_PERM',
        70 => 'CURLE_REMOTE_DISK_FULL',
        71 => 'CURLE_TFTP_ILLEGAL',
        72 => 'CURLE_TFTP_UNKNOWNID',
        73 => 'CURLE_REMOTE_FILE_EXISTS',
        74 => 'CURLE_TFTP_NOSUCHUSER',
        75 => 'CURLE_CONV_FAILED',
        76 => 'CURLE_CONV_REQD',
        77 => 'CURLE_SSL_CACERT_BADFILE',
        78 => 'CURLE_REMOTE_FILE_NOT_FOUND',
        79 => 'CURLE_SSH',
        80 => 'CURLE_SSL_SHUTDOWN_FAILED',
        81 => 'CURLE_AGAIN',
        82 => 'CURLE_SSL_CRL_BADFILE',
        83 => 'CURLE_SSL_ISSUER_ERROR',
        84 => 'CURLE_FTP_PRET_FAILED',
        85 => 'CURLE_RTSP_CSEQ_ERROR',
        86 => 'CURLE_RTSP_SESSION_ERROR',
        87 => 'CURLE_FTP_BAD_FILE_LIST',
        88 => 'CURLE_CHUNK_FAILED',
        89 => 'CURLE_NO_CONNECTION_AVAILABLE',
        90 => 'CURLE_SSL_PINNEDPUBKEYNOTMATCH',
        91 => 'CURLE_SSL_INVALIDCERTSTATUS',
    );

}
